package deployr.core;

import static deployr.client.http.Streams.*;
import static deployr.core.Files.*;
import static java.lang.Math.*;
import static java.text.MessageFormat.*;
import static java.util.Arrays.*;
import static java.util.Collections.*;

import java.io.*;
import java.util.*;
import java.util.concurrent.atomic.*;

import org.omg.CORBA.*;

import deployr.client.api.*;

public class UpdateProcess {

	private final String projectKey;

	private final DeployrApi deployr;

	private String updateKey;

	private String prevChecksum;

	private Progress progress = new Progress(100);

	private WebApp currWebapp;

	private WebApp prevWebapp;

	private WebApp diffWebapp;

	private String awsAccessKey;

	private String awsSecretKey;

	private final String bucketName;

	public UpdateProcess(final DeployrApi deployr, final String projectKey, final String bucketName) {
		this.deployr = deployr;
		this.projectKey = projectKey;
		this.bucketName = bucketName;
	}

	protected void check(final String artifactId) {
		println("Will check again in 1 seconds");
		println("Will check again in 1 seconds");
		println("Will check again in 1 seconds");
		println("Will check again in 1 seconds");
		deployr.check(artifactId);
	}

	protected void cleanup() {
		progress(100, 100);
		println("Cleaning up temporary files...");
		delete(target(createdZips()));
		delete(target(defaultZips()));
		delete(targetDeployr());
	}

	private void zip(final WebApp webapp, final String type) {
		final int count = webapp.count(type);
		final String message = format("Cloning {0} {1} files.", count, type);
		println(message);
		deployr.cloning(projectKey, updateKey, message);
		webapp.zip(type, targetDeployr(type));
		progress(count);
	}

	private void copy(final WebApp webapp, final String type) {
		final int count = webapp.count(type);
		final String message = format("Cloning {0} {1} files.", count, type);
		println(message);
		deployr.cloning(projectKey, updateKey, message);
		webapp.copy(type, targetDeployr());
		progress(count);
	}

	public void zipClassesFiles(final WebApp webapp) {
		zip(webapp, "classes");
	}

	protected void cloningFiles() {
		progressNeed(4);
		copyClassesFiles(diffWebapp);
		copyLibFiles(diffWebapp);
		copyRootFiles(diffWebapp);
		copyWebinfFiles(diffWebapp);
		progress(4);
	}

	private void copyWebinfFiles(WebApp webapp) {
		copy(webapp, "webinf");
	}

	private void copyRootFiles(WebApp webapp) {
		copy(webapp, "root");
	}

	private void copyClassesFiles(WebApp webapp) {
		copy(webapp, "classes");
	}

	public void copyLibFiles(final WebApp webapp) {
		copy(webapp, "lib");
	}

	public void zipRootFiles(final WebApp webapp) {
		zip(webapp, "root");
	}

	public void zipWebinfFiles(final WebApp webapp) {
		zip(webapp, "webinf");
	}

	protected void closeUpdate() {
		println("Closing update: new version is ready to start serving.");
		deployr.closeUpdate(projectKey, updateKey);
		progress();
	}

	private Iterable<String> createdZips() {
		if (diffWebapp == null) {
			return emptyList();
		}
		return diffWebapp.createdZips();
	}

	protected void creatingStagingDirectory() {
		println("Creating staging directory");
		createTmp(target());
		createTmp(targetDeployr());
		progress();
	}

	private void createTmp(final String targetDeployr) {
		final File tempDir = new File(targetDeployr);
		tempDir.delete();
		if (!tempDir.exists()) {
			tempDir.mkdir();
		}
		tempDir.deleteOnExit();
	}

	private Iterable<String> defaultZips() {
		return asList("classes.part.1.zip", "root.part.1.zip", "lib.part.1.zip", "webinf.part.1.zip");
	}

	private void delete(final Iterable<String> targets) {
		for (final String target : targets) {
			delete(target);
		}
	}

	protected void delete(final String pathname) {
		final File fileOrDir = new File(pathname);
		fileOrDir.delete();
	}

	protected void deployingNewVersion() {
		println("Deploying new version.");
		deployr.deployingNewVersion(projectKey, updateKey);
	}

	protected void initiatingUpdate(final Path path) {
		progressNeed(5);
		println("Initiating update..");
		deployr.initiatingUpdate(projectKey, updateKey);

		prevChecksum = deployr.checksum(projectKey);

		prevWebapp = new WebAppBuilder().curr(prevChecksum).newWebApp();
		currWebapp = new WebAppBuilder().currRoot(path).newWebApp();
		diffWebapp = prevWebapp.diff(currWebapp);
		progress(5);
	}

	protected void println(final String string, final String... strings) {
		System.out.print(format("{0} ", progress.percent()));
		System.out.println(format(string, strings));
	}

	private void progress() {
		progress.add(1);
	}

	private void progress(final int i) {
		progress.add(i);
	}

	private void progress(final int now, final int end) {
		progress = new Progress(end, now);
	}

	private void progressNeed(final double remaining) {
		progress = progress.newProgress(remaining);
	}

	protected void startUpdate(final String artifactId) {
		progress(5, 100);
		updateKey = deployr.startUpdate(artifactId);
		println("start update {0}", updateKey);
	}

	private String str(final int i) {
		return new Integer(i).toString();
	}

	protected void success(final String artifactId) {
		progress(99, 100);
		if (successfulUpdated(artifactId)) {
			println("Success.");
			deployr.success(artifactId, updateKey);
		} else {
			println("FAILURE.");
			deployr.failure(artifactId, updateKey);
		}
	}

	private boolean successfulUpdated(final String artifactId) {
		boolean success = false;
		String remoteChecksum = deployr.checksum(artifactId).trim();
		String localeChecksum = currWebapp.checksum().trim();
		success = remoteChecksum.equals(localeChecksum);
		return success;
	}

	private String targetDeployr() {
		return target("deployr");
	}

	private String target() {
		return target("");
	}

	private Iterable<String> target(final Iterable<String> createdZips) {
		final List<String> targets = new LinkedList<String>();
		for (final String createdZip : createdZips) {
			targets.add(targetDeployr(createdZip));
		}
		return targets;
	}

	protected String targetDeployr(final String path, final String... strings) {
		return format(userDir("target/deployr/{0}"), format(path, strings));
	}

	protected String target(final String path, final String... strings) {
		return format(userDir("target/{0}"), format(path, strings));
	}

	public void update(final Path path) {
		createProject(projectKey);
		startUpdate(projectKey);
		creatingStagingDirectory();
		initiatingUpdate(path);
		if (diffWebapp.needUpdate()) {
			cloningFiles();
			uploadFiles();
			uploadingPatchDescription();
			deployingNewVersion();
			check(projectKey);
			closeUpdate();
		} else {
			thereAreAnyUpdates();
		}
		updateComplete(projectKey);
		success(projectKey);
		cleanup();
	}

	private void thereAreAnyUpdates() {
		println("There aren\'t any updates.");
	}

	private void createProject(String projectKey) {
		progressNeed(1);
		println("create project");
		deployr.createProject(projectKey);
		progress(1);
	}

	protected void updateComplete(final String artifactId) {
		println("Update complete.");
		deployr.updateComplete(artifactId, updateKey);
		progress(98, 100);
	}

	protected void uploadFiles() {
		progressNeed(diffWebapp.createdZipsSize());
		println("Uploading {0} files.", str(diffWebapp.createdZipsSize()));
		uploadFiles(createdZips());
		progress(97, 100);
	}

	private void uploadFiles(final Iterable<String> uploadingFiles) {
		
		final int MAX_CONCURRENT = 16;

		final AtomicInteger uploadedFiles = new AtomicInteger();

		Iterator<String> iterator = uploadingFiles.iterator();

		while (iterator.hasNext()) {

			Thread[] uploads = new Thread[MAX_CONCURRENT];

			final AtomicInteger someUploads = new AtomicInteger();

			int i = 0;

			for (; i < uploads.length && iterator.hasNext(); i++) {

				uploads[i] = new UploadThread(iterator.next()) {
					@Override
					protected void onAfter() {
						someUploads.incrementAndGet();
						uploadedFiles.incrementAndGet();
					}
					
					@Override
					protected void onUpload(String path) {
						final File file = new File(path);
						if (file.exists()) {
							deployr.upload(bucketName, updateKey, awsAccessKey, awsSecretKey, file);
						}
						progress();
					};
				};
			}

			for (int j = 0; j < i; j++) {
				uploads[j].start();
			}

			for (int j = 0; j < i; j++) {
				try {
					uploads[j].join();
				} catch (Exception e) {
					throw new RuntimeException(e);
				}
			}

			while (someUploads.get() < i) {
				try {
					Thread.sleep(100);
				} catch (Exception e) {
					throw new RuntimeException(e);
				}
			}

			println("Uploaded {0} files.", uploadedFiles.toString());

			progress(uploadedFiles.get());

		}
	}

	protected void uploadingPatchDescription() {
		progressNeed(4);
		println("Uploading patch description.");
		deployr.uploadingPatchDescription(projectKey, updateKey);
		deployr.updateProjectChecksum(projectKey, currWebapp.checksum());
		deployr.updateChecksum(projectKey, updateKey, diffWebapp.checksum());
		deployr.updateDiff(projectKey, updateKey, diffWebapp.diff());
		progress(4);
	}
}